package edu.hawaii.halealohacli.jcev.command;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.datatype.XMLGregorianCalendar;
import org.junit.Test;
import org.wattdepot.client.WattDepotClient;
import org.wattdepot.resource.source.jaxb.Source;
import org.wattdepot.util.tstamp.Tstamp;

/**
 * Tests the methods of the RankTowers class. This test assumes the existence of a functioning
 * WattDepot server at http://server.wattdepot.org:8190/wattdepot. Note that this test will fail if
 * the connection times out while the object for each test is being generated or a data request is
 * being made.
 * 
 * @author Jordan Takayama
 */
public class TestRankTowers {
  /**
   * The URL of the WattDepot server.
   */
  private String url = "http://server.wattdepot.org:8190/wattdepot/";
  
  /**
   * A WattDepot client which connects to the server.
   */
  private WattDepotClient sampleClient = new WattDepotClient(url);
  
  /**
   * The class being tested.
   */
  private Class<RankTowers> testClass = RankTowers.class;
  
  /**
   * A RankTowers object which is initialized with specific data for each test case.
   */
  private RankTowers testObject = null;
  
  /**
   * A Date representing the current date.
   */
  private Date current = new Date();
  
  /**
   * Parses Dates into the simple date format used by WattDepot.
   */
  private SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
  
  /**
   * A string representing the name of the method "setAll", which this class frequently invokes.
   */
  private String setAll = "setAll";
  
  /**
   * A format which assumes a Hale Aloha tower's associated Source's name contains 
   * characters a-z and A-Z, with no dashes or whitespace.
   */
  private static final Pattern TOWER_PATTERN = Pattern.compile("^[a-zA-Z]+$");

  /**
   * Generates RankTowers objects for testing.
   * 
   * @param startDate The starting date for data collection, in the form yyyy-MM-dd.
   * @param endDate The ending date for data collection, in the form yyyy-MM-dd.
   * @param client The WattDepotClient used to request data from the server.
   * @return A RankTower object created based on the given parameters.
   * @throws Exception If an internal method call throws an exception.
   */
  private RankTowers makeRankTowers(String startDate, String endDate, WattDepotClient client)
      throws Exception {
    Method runSetAll =
        testClass.getDeclaredMethod(setAll, List.class, String.class, String.class,
            WattDepotClient.class);
    runSetAll.setAccessible(true);

    List<Source> sources = client.getSources();
    List<Source> towers = new ArrayList<Source>();
    Matcher matchTower = null;
    // Identify the source names of towers and add them to the List.
    for (Source source : sources) {
      matchTower = TOWER_PATTERN.matcher(source.getName());
      if (matchTower.matches()) {
        towers.add(source);
      }
    }

    RankTowers ranking = new RankTowers();
    ranking = (RankTowers) runSetAll.invoke(ranking, towers, startDate, endDate, sampleClient);
    return ranking;
  }

  /**
   * Creates a RankTowers object with the default constructor, then checks that the returned object
   * is not null.
   * 
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testRankTowers() throws Exception {
    testObject = new RankTowers();
    System.out.println("Testing RankTowers default constructor:");
    assertNotNull("RankTowers default constructor returned null.", testObject);
  }

  /**
   * Invokes setAll on a RankTowers object, then checks that its fields have been initialized.
   * 
   * @throws NoSuchMethodException If the setAll method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetAll() throws NoSuchMethodException, Exception {
    Method runSetAll =
        testClass.getDeclaredMethod(setAll, List.class, String.class, String.class,
            WattDepotClient.class);
    runSetAll.setAccessible(true);

    // The object used in this test only collects data for the current day.
    String today = format.format(current);
    List<Source> sources = sampleClient.getSources();
    List<Source> towers = new ArrayList<Source>();
    Matcher matchTower = null;
    // Identify the source names of towers and add them to the List.
    for (Source source : sources) {
      matchTower = TOWER_PATTERN.matcher(source.getName());
      if (matchTower.matches()) {
        towers.add(source);
      }
    }

    testObject = new RankTowers();
    testObject = (RankTowers) runSetAll.invoke(testObject, towers, today, today, sampleClient);
    double[] checkEnergy = testObject.getEnergyRank();
    System.out.format("\nTesting setAll for dates " + today + " to " + today + ":\n");
    assertNotNull("setAll failed to initialize client", testObject.getClient());
    assertNotNull("setAll failed to initialize TowerList", testObject.getTowerList());
    assertNotNull("setAll failed to initialize startDate", testObject.getStartDate());
    assertNotNull("setAll failed to initialize endDate", testObject.getEndDate());
    assertNotNull("setAll failed to initialize startTimestamp", testObject.getStartTimestamp());
    assertNotNull("setAll failed to initialize endTimestamps", testObject.getEndTimestamps());
    assertNotNull("setAll failed to initialize energyRank", checkEnergy);
    // It is assumed that setAll will sort the array of energy consumption values
    // in ascending order before using the array to initialize energyRank.
    for (int i = 0; i < checkEnergy.length - 1; i++) {
      assertTrue("energyRank array values are not sorted in ascending order.",
          (checkEnergy[i] <= checkEnergy[i + 1]));
    }
    assertNotNull("setAll failed to initialize energyTable", testObject.getEnergyTable());
  }

  /**
   * Tests the execute method by intentionally triggering its exceptions.
   * 
   * @throws NoSuchMethodException If the execute method does not exist.
   */
  @Test
  public void testExecute() throws NoSuchMethodException {
    Method testExecute =
        testClass.getDeclaredMethod("execute", String.class, WattDepotClient.class);
    testExecute.setAccessible(true);
    String message = "Correctly threw exception: ";

    testObject = new RankTowers();
    System.out.println("Testing execute:");
    try {
      testObject.execute("", sampleClient);
      fail("Failed to throw an exception for input with no arguments.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("2011-11-13", sampleClient);
      fail("Failed to throw an exception for input with a blank second argument.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("2011-11-13 2011-12-13 foo", sampleClient);
      fail("Failed to throw an exception for input with more than two arguments.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("2011-May-05 2011-02-29", sampleClient);
      fail("Failed to throw an exception for start date not in yyyy-mm-dd format.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("2011-05-05 2011-June-Third", sampleClient);
      fail("Failed to throw an exception for end date not in yyyy-mm-dd format.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("2011-06-31 2011-11-13", sampleClient);
      fail("Failed to throw an exception for start date outside of calendar ranges.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("2011-02-17 2011-02-29", sampleClient);
      fail("Failed to throw an exception for end date outside of calendar ranges.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      testObject.execute("2011-02-28 2011-02-28", sampleClient);
      fail("Failed to throw an exception for overlapping start and end dates.");
    }
    catch (Exception e) {
      System.out.println(message + e.getMessage());
    }

    try {
      // This part of the test should not cause any exceptions to be thrown.
      String yesterday = generateYesterday(Calendar.getInstance(Locale.getDefault()));
      testObject.execute(yesterday + " " + yesterday, sampleClient);
    }
    catch (Exception e) {
      fail("Threw an exception when given acceptable input: " + e.getMessage());
    }
  }

  /**
   * Checks that the RankTowers object's WattDepotClient is equal to the client originally used to
   * instantiate it.
   * 
   * @throws NoSuchMethodException If the getClient method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetClient() throws NoSuchMethodException, Exception {
    Method testClient = testClass.getDeclaredMethod("getClient");
    testClient.setAccessible(true);
    String currentDate = format.format(current);
    testObject = makeRankTowers(currentDate, currentDate, sampleClient);

    System.out.println("Testing getClient: ");
    WattDepotClient checkClient = (WattDepotClient) testClient.invoke(testObject);
    assertEquals("Failed to retrieve client equal to original client.", checkClient, sampleClient);
  }

  /**
   * Checks that the RankTowers object's list of Tower sources is equal to another list obtained
   * from the same client.
   * 
   * @throws NoSuchMethodException If the getTowerList method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetTowerList() throws NoSuchMethodException, Exception {
    Method testGetTowerList = testClass.getDeclaredMethod("getTowerList");
    testGetTowerList.setAccessible(true);
    String currentDate = format.format(current);
    List<Source> sources = sampleClient.getSources();
    List<Source> towers = new ArrayList<Source>();
    // Assume the name of a Tower contains characters a-z and A-Z,
    // with no dashes or whitespace.
    Pattern towerPattern = Pattern.compile("^[a-zA-Z]+$");
    Matcher matchTower = null;
    // Identify the source names of towers and add them to the List.
    for (Source source : sources) {
      matchTower = towerPattern.matcher(source.getName());
      if (matchTower.matches()) {
        towers.add(source);
      }
    }
    testObject = makeRankTowers(currentDate, currentDate, sampleClient);
    assertEquals("List returned is not equal to one produced with the same information.", towers,
        testObject.getTowerList());
  }

  /**
   * Checks that the date-representing string (yyyy-MM-dd) returned by getStartDate is the same as
   * the string used to create the RankTowers object.
   * 
   * @throws NoSuchMethodException If the getStartDate method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetStartDate() throws NoSuchMethodException, Exception {
    Method testGetStartDate = testClass.getDeclaredMethod("getStartDate");
    testGetStartDate.setAccessible(true);
    String currentDate = format.format(current);

    testObject = makeRankTowers(currentDate, currentDate, sampleClient);
    assertEquals("Start date returned is not equal to the original start date.", currentDate,
        testObject.getStartDate());
  }

  /**
   * Checks that the date-representing string (yyyy-MM-dd) returned by getEndDate is the same as the
   * string used to create the RankTowers object.
   * 
   * @throws NoSuchMethodException If the getEndDate method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetEndDate() throws NoSuchMethodException, Exception {
    Method testGetEndDate = testClass.getDeclaredMethod("getEndDate");
    testGetEndDate.setAccessible(true);
    String currentDate = format.format(current);

    testObject = makeRankTowers(currentDate, currentDate, sampleClient);
    assertEquals("End date returned is not equal to the original end date.", currentDate,
        testObject.getEndDate());
  }

  /**
   * Checks that getStartTimestamp returns an XMLGregorianCalendar equal to one generated using the
   * same information used to create the RankTowers object which has the XMLGregorianCalendar.
   * 
   * @throws NoSuchMethodException If the getStartTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetStartTimestamp() throws NoSuchMethodException, Exception {
    Method testStartTimestamp = testClass.getDeclaredMethod("getStartTimestamp");
    Method runGmtOffsetString = testClass.getDeclaredMethod("gmtOffsetString", TimeZone.class);
    testStartTimestamp.setAccessible(true);
    runGmtOffsetString.setAccessible(true);

    // Check the timestamps for the previous day, because these will be fixed values.
    String yesterday = generateYesterday(Calendar.getInstance(Locale.getDefault()));
    TimeZone test = TimeZone.getDefault();

    testObject = makeRankTowers(yesterday, yesterday, sampleClient);
    System.out.println("Testing getStartTimestamp:");
    XMLGregorianCalendar lastMidnight =
        Tstamp.makeTimestamp(yesterday + "T00:00:00.000"
            + runGmtOffsetString.invoke(testObject, test));
    assertEquals("getStartTimestamp failed to return a timestamp equal to one produced with the "
        + "same information.", lastMidnight, testObject.getStartTimestamp());
  }

  /**
   * Checks that getEndTimestamp returns an XMLGregorianCalendar array equal to one generated using
   * the same information used to create the RankTowers object which has the array.
   * 
   * @throws NoSuchMethodException If the getStartTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetEndTimestamp() throws NoSuchMethodException, Exception {
    Method runGmtOffsetString = testClass.getDeclaredMethod("gmtOffsetString", TimeZone.class);
    runGmtOffsetString.setAccessible(true);

    // Check the timestamps for the previous day - if it checked for the current day, all of the
    // end timestamps might have different values.
    String yesterday = generateYesterday(Calendar.getInstance(Locale.getDefault()));
    TimeZone test = TimeZone.getDefault();

    testObject = makeRankTowers(yesterday, yesterday, sampleClient);
    XMLGregorianCalendar lastTime =
        Tstamp.makeTimestamp(yesterday + "T23:59:59.999"
            + runGmtOffsetString.invoke(testObject, test));
    XMLGregorianCalendar[] checkLastTimes = testObject.getEndTimestamps();
    System.out.println("Testing getEndTimestamps:");
    for (int i = 0; i < checkLastTimes.length; i++) {
      assertEquals("Expected end timestamp for yesterday (23:59:59.999) is not the same as source "
          + i + ":", lastTime, checkLastTimes[i]);
    }
  }

  /**
   * Checks that the array of energy consumption values returned by getEnergyRank is not null, that
   * all values in the array are greater than or equal to 0, and that the values match known good 
   * values for a known good date. In the event of a WattDepot server crash or other data loss, the 
   * dates in this code may need to be changed to account for the loss of old data logs. The current
   * date used is 2011-11-23. It is also assumed that values are formatted to a whole number.
   * 
   * @throws NoSuchMethodException If the getEnergyRank method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetEnergyRank() throws NoSuchMethodException, Exception {
    //String currentDate = format.format(current);
    // This test assumes there are only four towers.
    Double[] expectedValues = new Double[]{598.0, 599.0, 618.0, 709.0};
    String knownDate = "2011-11-23";
    testObject = makeRankTowers(knownDate, knownDate, sampleClient);
    double[] checkEnergy = testObject.getEnergyRank();
    System.out.println("Testing getEnergyRank:");
    // Check if the array is not null.
    assertNotNull("getEnergyRank returned null.", checkEnergy);
    // Logically, tower energy consumption values should always be greater than or equal to 0.
    // This test assumes there are only four towers.
    for (int i = 0; i < checkEnergy.length; i++) {
      assertTrue("The energyRank array should not have energy consumption values < 0.",
          (checkEnergy[i] >= 0));
      assertEquals("Energy consumed does not match expected value for " + knownDate + ".", 
          expectedValues[i], (Double) checkEnergy[i]);
    }
  }

  /**
   * Checks that getEnergyTable does not return null, and stores as many elements as there are
   * energy consumption measurements.
   * 
   * @throws NoSuchMethodException If the getEnergyTable method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetEnergyTable() throws NoSuchMethodException, Exception {
    String currentDate = format.format(current);
    testObject = makeRankTowers(currentDate, currentDate, sampleClient);
    double[] energyValues = testObject.getEnergyRank();
    Hashtable<Double, List<Source>> checkTable = testObject.getEnergyTable();

    System.out.println("Testing getEnergyTable:");
    // Check that each measurement in energyValues retrieves a Source from the hash table. Continue
    // to increment the energyValues index if multiple Sources have the same value.
    for (int i = 0; i < energyValues.length; i++) {
      List<Source> checkList = checkTable.get(energyValues[i]);
      assertNotNull("Sources were stored with the wrong key, or not stored at all.", checkList);
      if (checkList.size() > 1) {
        // If the list has multiple items, increment by number of items - 1, because i is being
        // incremented once at the end of the loop.
        i += checkList.size() - 1;
      }
    }
  }

  /**
   * Checks that setClient correctly sets a RankTowers object's uninitialized client.
   * 
   * @throws NoSuchMethodException If the setClient method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetClient() throws NoSuchMethodException, Exception {
    Method testSetClient = testClass.getDeclaredMethod("setClient", WattDepotClient.class);
    testSetClient.setAccessible(true);
    testObject = new RankTowers();

    System.out.println("Testing setClient: ");
    testSetClient.invoke(testObject, sampleClient);
    WattDepotClient setResult = testObject.getClient();
    assertNotNull("setClient failed to set the WattDepotClient.", setResult);
    assertEquals("setClient's set client is not equal to the WattDepotClient passed to it.",
        sampleClient, setResult);
  }

  /**
   * Checks that setTowerList correctly sets a RankTowers object's uninitialized towerList.
   * 
   * @throws NoSuchMethodException If the setTowerList method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetTowerList() throws NoSuchMethodException, Exception {
    Method testSetTowerList = testClass.getDeclaredMethod("setTowerList", List.class);
    testSetTowerList.setAccessible(true);
    testObject = new RankTowers();

    List<Source> sources = sampleClient.getSources();
    List<Source> towers = new ArrayList<Source>();
    // Assume the name of a Tower contains characters a-z and A-Z,
    // with no dashes or whitespace.
    Pattern towerPattern = Pattern.compile("^[a-zA-Z]+$");
    Matcher matchTower = null;
    // Identify the source names of towers and add them to the List.
    for (Source source : sources) {
      matchTower = towerPattern.matcher(source.getName());
      if (matchTower.matches()) {
        towers.add(source);
      }
    }

    testSetTowerList.invoke(testObject, towers);
    List<Source> setResult = testObject.getTowerList();
    assertNotNull("setTowerList failed to set the list of towers.", setResult);
    assertEquals("setTowerList's set tower list is not equal to the list passed to it.", towers,
        setResult);
  }

  /**
   * Checks that setStartDate correctly sets a RankTowers object's uninitialized startDate.
   * 
   * @throws NoSuchMethodException If the setStartDate method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetStartDate() throws NoSuchMethodException, Exception {
    Method testSetStartDate = testClass.getDeclaredMethod("setStartDate", String.class);
    testSetStartDate.setAccessible(true);
    testObject = new RankTowers();

    System.out.println("Testing setStartDate: ");
    String testStart = "2010-11-11";
    testSetStartDate.invoke(testObject, testStart);
    String setResult = testObject.getStartDate();
    assertNotNull("setStartDate fails to set the start date.", setResult);
    assertEquals("setStartDate's set date string is not equal to the string passed to it.",
        testStart, setResult);
  }

  /**
   * Checks that setEndDate correctly sets a RankTowers object's uninitialized endDate.
   * 
   * @throws NoSuchMethodException If the setEndDate method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetEndDate() throws NoSuchMethodException, Exception {
    Method testSetEndDate = testClass.getDeclaredMethod("setEndDate", String.class);
    testSetEndDate.setAccessible(true);
    testObject = new RankTowers();

    System.out.println("Testing setEndDate: ");
    String testStart = "2010-11-11";
    testSetEndDate.invoke(testObject, testStart);
    String setResult = testObject.getEndDate();
    assertNotNull("setEndDate fails to set the end date.", setResult);
    assertEquals("setEndDate's set date string is not equal to the string passed to it.",
        testStart, setResult);
  }

  /**
   * Checks that setStartTimestamp correctly sets a RankTowers object's uninitialized
   * startTimestamp.
   * 
   * @throws NoSuchMethodException If the setStartTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetStartTimestamp() throws NoSuchMethodException, Exception {
    Method testSetStart =
        testClass.getDeclaredMethod("setStartTimestamp", XMLGregorianCalendar.class);
    testSetStart.setAccessible(true);
    testObject = new RankTowers();

    System.out.println("Testing setStartTimestamp: ");
    XMLGregorianCalendar sampleTimestamp = Tstamp.makeTimestamp("2011-11-11T11:11:11.111-11:00");
    testSetStart.invoke(testObject, sampleTimestamp);
    XMLGregorianCalendar setResult = testObject.getStartTimestamp();
    assertNotNull("setStartTimestamp failed to set the XMLGregorianCalendar", setResult);
    assertEquals("setStartTimestamp's set XMLGregorianCalendar is not equal to the "
        + "XMLGregorianCalendar passed to it.", sampleTimestamp, setResult);
  }

  /**
   * Checks that setEndTimestamps correctly sets a RankTowers object's uninitialized EndTimestamps.
   * 
   * @throws NoSuchMethodException If the setEndTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetEndTimestamps() throws NoSuchMethodException, Exception {
    // Making XMLGregorianCalendar[] a new class is the only way to invoke the method correctly.
    Method testSetEnd =
        testClass.getDeclaredMethod("setEndTimestamps", new Class[]{XMLGregorianCalendar[].class});
    testSetEnd.setAccessible(true);
    testObject = new RankTowers();

    System.out.println("Testing setEndTimestamps: ");
    XMLGregorianCalendar sampleTimestamp = Tstamp.makeTimestamp("2011-11-11T11:11:11.111-11:00");
    XMLGregorianCalendar[] holdTimestamps = new XMLGregorianCalendar[]{sampleTimestamp, 
        sampleTimestamp};
    // Cast the array to an object, otherwise it will be treated as varargs.
    testSetEnd.invoke(testObject, new Object[]{holdTimestamps});
    XMLGregorianCalendar[] setResults = testObject.getEndTimestamps();
    assertNotNull("setEndTimestamps failed to set the XMLGregorianCalendar array.", setResults);
    for (int i = 0; i < setResults.length; i++) {
      assertEquals("setEndTimestamps's set XMLGregorianCalendar is not equal to the "
          + "XMLGregorianCalendar passed to it.", holdTimestamps[i], setResults[i]);
    }
  }

  /**
   * Checks that setEnergyRank correctly sets a RankTowers objects' uninitialized EnergyRank.
   * 
   * @throws NoSuchMethodException If the setEnergyRank method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetEnergyRank() throws NoSuchMethodException, Exception {
    Method testSetEnergy = testClass.getDeclaredMethod("setEnergyRank", double[].class);
    testSetEnergy.setAccessible(true);
    testObject = new RankTowers();
    double[] testRank = new double[] {200.7, 300.15, 800.07, 1000.10 };
    testSetEnergy.invoke(testObject, testRank);
    double[] setResult = testObject.getEnergyRank();
    assertNotNull("setEnergyRank failed to set the EnergyRank array.", setResult);
    for (int i = 0; i < testRank.length; i++) {
      assertTrue("setEnergyRank's set array of doubles is not equal to the array passed to it.",
          (testRank[i] == setResult[i]));
    }
  }

  /**
   * Checks that setEnergyTable correctly sets a RankTowers object's uninitialized EnergyTable.
   * 
   * @throws NoSuchMethodException If the setEnergyTable method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSetEnergyTable() throws NoSuchMethodException, Exception {
    Method testSetTable = testClass.getDeclaredMethod("setEnergyTable", Hashtable.class);
    testSetTable.setAccessible(true);
    testObject = new RankTowers();

    // Make keys and lists to add to hash table. Negative keys do not overlap with normal results.
    List<Source> allSources = sampleClient.getSources();
    Double testKey = 2011.32;
    Double testSameKey = 3217.33;
    List<Source> testKeyList = new ArrayList<Source>();
    List<Source> sameKeyList = new ArrayList<Source>();
    Hashtable<Double, List<Source>> testTable = new Hashtable<Double, List<Source>>();
    if (allSources.size() >= 2) {
      testKeyList.add(allSources.get(0));
      sameKeyList.add(allSources.get(0));
      sameKeyList.add(allSources.get(1));

      System.out.println("Testing setEnergyTable: ");
      sameKeyList.add(allSources.get(1));
      testTable.put(testKey, testKeyList);
      testTable.put(testSameKey, sameKeyList);
      testSetTable.invoke(testObject, testTable);
      Hashtable<Double, List<Source>> checkTable = testObject.getEnergyTable();
      assertNotNull("setEnergyTable failed to set the EnergyTable hash table.", checkTable);
      assertEquals("setEnergyTable's hash table is not equal to the hash table passed to it.",
          testTable, checkTable);
    }
    else {
      System.out.println("The client has less than two sources. This test cannot be conducted.");
    }
  }

  /**
   * Checks that Sources are successfully added by putInEnergyTable.
   * 
   * @throws NoSuchMethodException If putInEnergyTable does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testPutInEnergyTable() throws NoSuchMethodException, Exception {
    Method testPut = testClass.getDeclaredMethod("putInEnergyTable", Double.class, List.class);
    testPut.setAccessible(true);
    String currentDate = format.format(current);
    List<Source> allSources = sampleClient.getSources();
    testObject = makeRankTowers(currentDate, currentDate, sampleClient);
    // Make keys and lists to add to hash table. Negative keys do not overlap with normal results.
    Double testKey = new Double(-3133.01);
    Double testSameKey = new Double(-2256.34);
    List<Source> testKeyList = new ArrayList<Source>();
    List<Source> sameKeyListA = new ArrayList<Source>();
    List<Source> sameKeyListB = new ArrayList<Source>();
    if (allSources.size() >= 2) {
      testKeyList.add(allSources.get(0));
      sameKeyListA.add(allSources.get(0));
      sameKeyListB.add(allSources.get(1));

      System.out.println("Testing putInEnergyTable: ");
      // Insert a value using the test key.
      testPut.invoke(testObject, testKey, testKeyList);
      // Insert a value using the same key twice.
      testPut.invoke(testObject, testSameKey, sameKeyListA);
      testPut.invoke(testObject, testSameKey, sameKeyListB);
      // Add the source in sameKeyListB to sameKeyListA to simulate what should
      // be happening in putInEnergyTable when duplicate keys are used.
      sameKeyListA.add(allSources.get(1));
      Hashtable<Double, List<Source>> checkPut = testObject.getEnergyTable();
      assertNotNull("putInEnergyTable failed to add a key-value pair to the hash table.",
          checkPut.get(testKey));
      assertEquals("putInEnergyTable failed to merge Sources with the same key into one List.",
          sameKeyListA, checkPut.get(testSameKey));
    }
    else {
      System.out.println("The client has less than two sources. This test cannot be conducted.");
    }
  }

  /**
   * Checks that correctly formatted strings representing GMT offsets are being returned by
   * gmtOffsetString.
   * 
   * @throws NoSuchMethodException If the gmtOffsetString method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGmtOffsetString() throws NoSuchMethodException, Exception {
    Method testOffset = testClass.getDeclaredMethod("gmtOffsetString", TimeZone.class);
    testOffset.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new RankTowers();

    System.out.println("Testing gmtOffsetString: ");
    // Test for positive offset < 10 hours
    assertEquals("GMT offset for Japan (JST) should be +09:00", "+09:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("JST"))));
    // Test for positive offset > 10 hours
    assertEquals("GMT offset for New Zealand (NZ) should be +12:00", "+12:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("NZ"))));
    // Test for 0 hours
    assertEquals("GMT offset for GMT should be +00:00", "+00:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("GMT"))));
    // Test for negative offset < 10 hours
    assertEquals("GMT offset for Pacific Standard Time (PST) should be -08:00", "-08:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("PST"))));
    // Test for negative offset > 10 hours
    assertEquals("GMT offset for Hawaiian Standard Time (HST) should be -10:00", "-10:00",
        ((String) testOffset.invoke(testObject, TimeZone.getTimeZone("HST"))));
  }

  /**
   * Check that XMLGregorianCalendars with correct information are being returned by
   * generateTimestamp.
   * 
   * @throws NoSuchMethodException If the generateTimestamp method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGenerateTimestamp() throws NoSuchMethodException, Exception {
    Method testGenerateTimestamp =
        testClass
            .getDeclaredMethod("generateTimestamp", String.class, String.class, TimeZone.class);
    testGenerateTimestamp.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new RankTowers();

    System.out.println("Testing generateTimestamp: ");
    TimeZone testZone = TimeZone.getTimeZone("PST");
    XMLGregorianCalendar testCalendar =
        (XMLGregorianCalendar) testGenerateTimestamp.invoke(testObject, "2009-04-13",
            "T00:00:00.000", testZone);
    XMLGregorianCalendar compareCalendar = Tstamp.makeTimestamp("2009-04-13T00:00:00.000-08:00");
    assertEquals("generateTimestamp should make a timestamp logically equal to a "
        + "timestamp generated based on the same information", compareCalendar, testCalendar);
    // Test that the exception is correctly thrown.
    try {
      XMLGregorianCalendar failure =
          (XMLGregorianCalendar) testGenerateTimestamp.invoke(testObject, "2001-december-eighth",
              "T00:00:00.000", testZone);
      fail("Failed to throw exception for incorrectly formatted date: " + failure.toString());
    }
    catch (Exception e) {
      System.out.println("Correctly threw exception: " + e.getMessage());
    }
  }

  /**
   * Check that the date format is correctly being tested by isDateFormatCorrect.
   * 
   * @throws NoSuchMethodException If the isDateFormatCorrect method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testIsDateFormatCorrect() throws NoSuchMethodException, Exception {
    Method testDateFormat = testClass.getDeclaredMethod("isDateFormatCorrect", String.class);
    testDateFormat.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new RankTowers();

    System.out.println("Testing isDateFormatCorrect: ");
    // Test incorrect date formats
    assertFalse("11-12-11 is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "11-12-11"));
    assertFalse("November 12 2011 is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "November 12 2011"));
    assertFalse("November-12-2011 is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "November-12-2011"));
    assertFalse("2011-Nov-12 is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "2011-Nov-12"));
    assertFalse("2011-12-Eleven is not in the format yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "2011-12-Eleven"));
    assertFalse("####-##-## does not use digits for yyyy-mm-dd.",
        (Boolean) testDateFormat.invoke(testObject, "####-##-##"));
    // Test correct date format
    assertTrue("2011-11-12 is in the format yyyy-mm-dd and should pass.",
        (Boolean) testDateFormat.invoke(testObject, "2011-11-12"));
  }

  /**
   * Checks that inCalendarRanges is not accepting incorrect dates.
   * 
   * @throws NoSuchMethodException If the inCalendarRanges method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testInCalendarRanges() throws NoSuchMethodException, Exception {
    Method testRanges = testClass.getDeclaredMethod("inCalendarRanges", String.class);
    testRanges.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new RankTowers();

    System.out.println("Testing inCalendarRanges: ");
    // Test incorrect dates
    assertFalse("1998-10-56 is an invalid date.",
        (Boolean) testRanges.invoke(testObject, "1998-10-56"));
    assertFalse("2000-18-11 is an invalid date.",
        (Boolean) testRanges.invoke(testObject, "2000-18-11"));
    assertFalse("1933-02-29 is an invalid date (no leap year).",
        (Boolean) testRanges.invoke(testObject, "1933-02-29"));
    assertFalse("1900-02-29 is an invalid date (no leap year if divisible by 100 but not by 400).",
        (Boolean) testRanges.invoke(testObject, "1911-02-29"));
    assertTrue("2000-02-29 is a valid date (leap year).",
        (Boolean) testRanges.invoke(testObject, "2000-02-29"));
    assertTrue("2009-01-31 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-01-31"));
    assertTrue("2009-02-28 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-02-28"));
    assertTrue("2009-03-31 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-03-31"));
    assertTrue("2009-04-30 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-04-30"));
    assertTrue("2009-05-31 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-05-22"));
    assertTrue("2009-06-30 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-06-30"));
    assertTrue("2009-07-31 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-07-31"));
    assertTrue("2009-08-31 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-08-31"));
    assertTrue("2009-09-30 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-09-30"));
    assertTrue("2009-10-31 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-10-31"));
    assertTrue("2009-11-30 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-11-30"));
    assertTrue("2009-12-31 is a valid date.", 
        (Boolean) testRanges.invoke(testObject, "2009-12-31"));
  }

  /**
   * Checks that noDateConflict is correctly evaluating whether start date and end date combinations
   * do not conflict with each other and do not exceed the current date.
   * 
   * @throws NoSuchMethodException If the noDateConflict method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testNoDateConflict() throws NoSuchMethodException, Exception {
    Method testDateConflict =
        testClass.getDeclaredMethod("noDateConflict", String.class, String.class);
    testDateConflict.setAccessible(true);

    Calendar currentCalendar = Calendar.getInstance();
    int year = currentCalendar.get(Calendar.YEAR);
    int month = currentCalendar.get(Calendar.MONTH);
    int day = currentCalendar.get(Calendar.DAY_OF_MONTH);
    String currentDate;
    String yearString = Integer.toString(year);
    String monthString = Integer.toString(month);
    String dayString = Integer.toString(day);
    String testDate;
    if (month < 10) {
      monthString = "0" + month;
    }
    if (day < 10) {
      dayString = "0" + day;
    }
    currentDate = yearString + "-" + monthString + "-" + dayString;

    System.out.println("Testing noDateConflict.");
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new RankTowers();
    assertFalse("Incorrectly validated a start year after the end year: 2011-12-11 to 2010-01-13",
        (Boolean) testDateConflict.invoke(testObject, "2011-12-11", "2010-01-13"));
    assertFalse(
        "Incorrectly validated a start month after the end month: 2010-03-11 to 2010-02-15",
        (Boolean) testDateConflict.invoke(testObject, "2010-03-11", "2010-02-15"));
    assertFalse("Incorrectly validated a start day after the end day: 2011-03-18 to 2011-03-15",
        (Boolean) testDateConflict.invoke(testObject, "2011-03-18", "2011-03-15"));
    testDate = (year + 1) + "-" + monthString + "-" + dayString;
    assertFalse("Incorrectly validated a start date exceeding the current year: " + testDate + ".",
        (Boolean) testDateConflict.invoke(testObject, testDate, testDate));
    // If it is currently December, this test will check for rejection of "month 13."
    // This test checks whether the current date is exceeded, not if it exists.
    if (month < 9) {
      testDate = yearString + "-" + "0" + (month + 1) + "-" + dayString;
    }
    else {
      testDate = yearString + "-" + (month + 1) + "-" + dayString;
    }
    assertFalse(
        "Incorrectly validated a start date exceeding the current month: " + testDate + ".",
        (Boolean) testDateConflict.invoke(testObject, testDate, currentDate));
    // If today is the last day of the month, this test will check for rejection of a day past the
    // end of the month, e.g., "2011-01-32". This test checks whether the current date is
    // exceeded, not whether that day of the month exists.
    if (day < 9) {
      testDate = yearString + "-" + monthString + "-" + "0" + (day + 1);
    }
    else {
      testDate = yearString + "-" + monthString + "-" + (day + 1);
    }
    assertFalse("Incorrectly validated a start date exceeding the current day: " + testDate + ".",
        (Boolean) testDateConflict.invoke(testObject, testDate, currentDate));
  }

  /**
   * Tests that the sortAscending method correctly sorts an array of doubles in ascending order.
   * 
   * @throws NoSuchMethodException If the sortAscending method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testSortAscending() throws NoSuchMethodException, Exception {
    Method testSort = testClass.getDeclaredMethod("sortAscending", double[].class);
    testSort.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new RankTowers();

    System.out.println("Testing sortAscending: ");
    double[] inOrder = new double[] { -3.6, -3.14, -2.2, -1.5, 0.0, 3.11, 4.13, 6.12, 7.8, 9.3 };
    double[] outOfOrder = new double[] { 6.12, -3.14, 7.8, -2.2, 4.13, 0.0, -1.5, 9.3, -3.6, 3.11 };
    outOfOrder = (double[]) testSort.invoke(testObject, outOfOrder);
    boolean isOrdered = true;
    for (int i = 0; i < inOrder.length; i++) {
      if (inOrder[i] != outOfOrder[i]) {
        isOrdered = false;
      }
    }
    assertTrue("Unsorted array is not properly sorted into ascending order.", isOrdered);
  }

  /**
   * Checks that toString does not return null.
   * 
   * @throws NoSuchMethodException If the toString method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testToString() throws NoSuchMethodException, Exception {
    Method testToString = testClass.getDeclaredMethod("toString");
    testToString.setAccessible(true);
    String currentDate = generateYesterday(Calendar.getInstance(Locale.getDefault()));
    // Again, find a way to avoid having to hard-code this date.
    
    testObject = makeRankTowers(currentDate, format.format(current), sampleClient);

    System.out.println("Testing toString: ");
    String checkToString = (String) testToString.invoke(testObject);
    assertNotNull("toString returned null.", checkToString);
  }

  /**
   * Checks that getHelpString does not return null.
   * 
   * @throws NoSuchMethodException If the getHelpString method does not exist.
   * @throws Exception If a method call within the test throws an exception.
   */
  @Test
  public void testGetHelpString() throws NoSuchMethodException, Exception {
    Method testHelpString = testClass.getDeclaredMethod("getHelpString");
    testHelpString.setAccessible(true);
    // The method manipulates no fields in the below RankTowers object; setAll is not needed.
    testObject = new RankTowers();

    System.out.println("Testing getHelpString: ");
    String checkHelp = (String) testHelpString.invoke(testObject);
    assertNotNull("getHelpString returned null.", checkHelp);
  }

  /**
   * A utility method that returns a string (yyyy-MM-dd) representing the date before today.
   * 
   * @param calendar A Calendar object representing today's date.
   * @return A string in the form yyyy-MM-dd representing the date yesterday.
   */
  private String generateYesterday(Calendar calendar) {
    // Roll back the date.
    calendar.add(Calendar.DATE, -1);
    int year = calendar.get(Calendar.YEAR);
    // Account for Calendar months starting at 0 instead of 1.
    int month = calendar.get(Calendar.MONTH) + 1;
    int day = calendar.get(Calendar.DAY_OF_MONTH);
    String monthString;
    String dayString;
    
    if (month < 10) {
      monthString = "0" + month;
    }
    else {
      monthString = Integer.toString(month);
    }
    if (day < 10) {
      dayString = "0" + day;
    }
    else {
      dayString = Integer.toString(day);
    }
    return year + "-" + monthString + "-" + dayString;
  }
}